package com.pureland.common.entity;


import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pureland.common.constants.FormatConstants;
import com.pureland.common.entity.enums.CommonError;
import com.pureland.common.entity.enums.IntEnum;
import com.pureland.common.exception.BaseExceptionCode;
import com.pureland.common.exception.BizException;
import com.pureland.common.exception.ExceptionCode;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;


/**
 * @projectName: pureland_cloud
 * @className: com.pureland.common.entity.R
 * @description: 统一API响应实体类封装
 * @author: tong.li
 * @createTime: 2020/9/22 20:24
 * @version: v1.0
 * @copyright: 版权所有 李彤 © 2020
 */
@Getter
@Setter
@SuppressWarnings({"AlibabaClassNamingShouldBeCamel"})
@Accessors(chain = true)
public class R<T> implements Serializable {

    private static final long serialVersionUID = 7357883468547892901L;

    private static final Logger logger = LoggerFactory.getLogger(R.class);

    private static final ObjectMapper objectMapper = new ObjectMapper();


    public static final String OPERATION_SUCCESS = "操作成功!";
    public static final String DEF_ERROR_MESSAGE = "系统繁忙，请稍候再试";
    public static final String HYSTRIX_ERROR_MESSAGE = "请求超时，请稍候再试";
    public static final int SUCCESS_CODE = 200;
    public static final int FAIL_CODE = -1;
    public static final int TIMEOUT_CODE = -2;
    /**
     * 统一参数验证异常
     */
    public static final int VALID_EX_CODE = -9;
    public static final int OPERATION_EX_CODE = -400;


    private int code;

    /**
     * 是否执行默认操作
     */
    @JsonIgnore
    private Boolean defExec = false;

    @ApiModelProperty("是否成功")
    private boolean successful = true;

    /**
     * 消息id
     */
    @ApiModelProperty(value = "消息id")
    private int messageId;

    /**
     * 消息内容
     */
    @ApiModelProperty(value = "消息内容")
    private String message;
    /**
     * 时间戳：Date 类型
     */
    @ApiModelProperty(value = "时间戳")
    @DateTimeFormat(pattern = FormatConstants.NORM_DATETIME_PATTERN)
    @JsonFormat(pattern = FormatConstants.NORM_DATETIME_PATTERN, timezone = "GMT+8")
    private LocalDateTime timestamp;
    /**
     * 返回数据
     */
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty(value = "返回数据")
    private T data;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @ApiModelProperty(value = "拓展字段")
    private Map<String, Object> ext;


    private R(Builder<T> builder) {
        this.messageId = builder.messageId;
        this.message = builder.message;
        this.timestamp = builder.timestamp;
        this.successful = builder.successful;
        this.code = builder.successful ? 1 : 0;
        this.data = builder.data;
        this.ext = builder.ext;
    }

    private R() {
        super();
    }


    public R(int messageId, T data, String message) {
        this.messageId = messageId;
        this.data = data;
        this.message = message;
        this.code = messageId == SUCCESS_CODE ? 1 : 0;
        this.successful = messageId == SUCCESS_CODE;
        this.defExec = false;
        this.timestamp = LocalDateTime.now();
    }

    public R(int messageId, T data, String message, boolean successful, boolean defExec) {
        this.messageId = messageId;
        this.data = data;
        this.message = message;
        this.successful = successful;
        this.defExec = defExec;
        this.timestamp = LocalDateTime.now();
    }

    /**
     * 请求成功消息
     *
     * @return RPC调用结果
     */
    public static <E> R<E> success() {
        return new R<>(SUCCESS_CODE, null, OPERATION_SUCCESS);
    }


    public static <E> R<E> result(int messageId, E data, String msg) {
        return new R<>(messageId, data, msg);
    }

    public static <E> R<E> successDef(E data) {
        return new R<>(SUCCESS_CODE, data, OPERATION_SUCCESS, true, true);
    }

    public static <E> R<E> successDef() {
        return new R<>(SUCCESS_CODE, null, OPERATION_SUCCESS, true, true);
    }

    public static <E> R<E> successDef(E data, String msg) {
        return new R<>(SUCCESS_CODE, data, msg, true, true);
    }


    public static R<String> SUCCESS = getResponse(SUCCESS_CODE, OPERATION_SUCCESS, true, null);


    public static <T> R<T> success(T data) {
        return getResponse(SUCCESS_CODE, OPERATION_SUCCESS, true, data);
    }

    public static <T> R<T> success(String message, T data) {
        return getResponse(SUCCESS_CODE, message, true, data);
    }

    public static <T> R<T> success(String message) {
        return success(message, null);
    }

    public static R<String> success(int messageId) {
        return success(messageId, OPERATION_SUCCESS);
    }

    public static R<String> success(int messageId, String message) {
        return success(messageId, message, null);
    }

    public static <T> R<T> ok(T data) {
        return success(SUCCESS_CODE, OPERATION_SUCCESS, data);
    }

    public static <T> R<T> success(int messageId, T data) {
        return getResponse(messageId, OPERATION_SUCCESS, true, data);
    }

    public static <T> R<T> exists(String label, Map<String, Object> ext) {
        final int messageId = CommonError.DATA_EXISTS.type();
        final String message = String.format(CommonError.DATA_EXISTS.desc(), label);
        return new Builder<T>(messageId, LocalDateTime.now(), false)
                .message(message).ext(ext).data(null).build();
    }

    public static <E> R<E> fail(String msg, Object... args) {
        String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
        return new R<>(OPERATION_EX_CODE, null, String.format(message, args));
    }

    public static <E> R<E> fail() {
        return validFail(ExceptionCode.BAD_REQUEST);
    }

    public static <E> R<E> fail(ExceptionCode exceptionCode) {
        return validFail(exceptionCode);
    }

    public static <E> R<E> fail(BaseExceptionCode exceptionCode) {
        return validFail(exceptionCode);
    }

    /**
     * 请求失败消息，根据异常类型，获取不同的提供消息
     *
     * @param throwable 异常
     * @return RPC调用结果
     */
    public static <E> R<E> fail(Throwable throwable) {
        return fail(FAIL_CODE, throwable != null ? throwable.getMessage() : DEF_ERROR_MESSAGE);
    }


    public static <E> R<E> fail(BizException exception) {
        if (exception == null) {
            return fail(DEF_ERROR_MESSAGE);
        }
        return new R<>(exception.getCode(), null, exception.getMessage());
    }

    public static <E> R<E> validFail(String msg) {
        return new R<>(VALID_EX_CODE, null, (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg);
    }

    public static <E> R<E> validFail(String msg, Object... args) {
        String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
        return new R<>(VALID_EX_CODE, null, String.format(message, args));
    }

    public static <E> R<E> validFail(BaseExceptionCode exceptionCode) {
        return new R<>(exceptionCode.getCode(), null,
                (exceptionCode.getMsg() == null || exceptionCode.getMsg().isEmpty()) ? DEF_ERROR_MESSAGE : exceptionCode.getMsg(), false, false);
    }

    public static <T> R<T> success(int messageId, String message, T data) {
        return getResponse(messageId, message, true, data);
    }

    public static <T> R<T> fail(String message, T data) {
        return getResponse(400, message, false, data);
    }

    public static <T> R<T> fail(String message) {
        // 对于 varargs调用, 应使用Object
        // 对于非varargs调用, 应使用 Object[], 这样也可以抑制此警告
        return fail(message, new Object[]{});
    }

    public static <T> R<T> fail(IntEnum intEnum) {
        return getResponse(intEnum.type(), intEnum.desc(), false, null);
    }

    public static <T> R<T> fail(IntEnum intEnum, T data) {
        return getResponse(intEnum.type(), intEnum.desc(), false, data);
    }

    public static <T> R<T> fail(IntEnum intEnum, String format) {
        return getResponse(intEnum.type(), String.format(intEnum.desc(), format), false, null);
    }

    public static <T> R<T> fail(int messageId, String message) {
        return getResponse(messageId, message, false, null);
    }

    public static <T> R<T> fail(int messageId, String message, T data) {
        return getResponse(messageId, message, false, data);
    }

    public static <T> R<T> getResponse(Boolean flag) {
        return flag ? success(OPERATION_SUCCESS) : fail(DEF_ERROR_MESSAGE);
    }

    public static <T> R<T> getResponse(boolean successful, String message) {
        return successful ? success(message) : fail(message);
    }

    public static <T> R<T> getResponse(boolean successful, String message, T data) {
        return successful ? success(message, data) : fail(message);
    }

    public static <T> R<T> getResponse(int messageId, String message, boolean successful, T data) {
        return new Builder<T>(messageId, LocalDateTime.now(), successful).message(message).data(data).build();
    }

    public static class Builder<T> {
        private final int messageId;
        private String message;
        @DateTimeFormat(pattern = FormatConstants.NORM_DATETIME_PATTERN)
        @JsonFormat(pattern = FormatConstants.NORM_DATETIME_PATTERN, timezone = "GMT+8")
        private final LocalDateTime timestamp;
        private final boolean successful;
        private T data;
        private Map<String, Object> ext;

        public Builder(int messageId, LocalDateTime timestamp, boolean successful) {
            this.messageId = messageId;
            this.timestamp = timestamp;
            this.successful = successful;
        }

        public Builder<T> message(String message) {
            this.message = message;
            return this;
        }

        public Builder<T> ext(Map<String,Object> ext) {
            this.ext = ext;
            return this;
        }

        public Builder<T> data(T data) {
            this.data = data;
            return this;
        }

        public R<T> build() {
            return new R<>(this);
        }
    }

    @Override
    public String toString(){
        try {
            return objectMapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            logger.error(this.getClass() + "#toString error cause : JSON Serial",e);
        }
        return null;
    }

    public byte[] toByte() throws JsonProcessingException {
        return  objectMapper.writeValueAsBytes(this);
    }

}
